/**
 * Copyright 2011 T. Spilman
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.jreg;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.apache.commons.exec.ExecuteException;

import com.jreg.Operation.AddOperation;
import com.jreg.Operation.CopyOperation;
import com.jreg.Operation.DeleteOperation;
import com.jreg.Operation.ExportOperation;
import com.jreg.Operation.ImportOperation;
import com.jreg.Operation.LoadOperation;
import com.jreg.Operation.QueryOperation;
import com.jreg.Operation.RestoreOperation;
import com.jreg.Operation.SaveOperation;
import com.jreg.Operation.UnloadOperation;

public class RegistryKey {

	private static final int MAX_KEY_LENGTH = 255;
	private static final long DEFAULT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);

	private String name;
	private long timeout;

	public RegistryKey(String keyName) {
		setName(keyName);
		setTimeout(DEFAULT_TIMEOUT);
	}

	public String getName() {
		return name;
	}

	private void setName(String name) {
		if (name == null)
			throw new IllegalArgumentException("name must not be null");
		else if (name.length() > MAX_KEY_LENGTH)
			throw new IllegalArgumentException("name is too long");
		this.name = name;
	}

	public long getTimeout() {
		return timeout;
	}

	public void setTimeout(long timeout) {
		this.timeout = timeout;
	}

	public RegistryKey getSubkey(String keyName) throws RegistryException {
		for (RegistryKey subkey : getSubKeys()) {
			String subkeyName = subkey.getName();
			if (subkeyName.endsWith(keyName))
				return subkey;
		}
		return null;
	}

	/**
	 * 
	 * @return an unmodifiable list
	 * @throws RegistryException
	 */
	public List<RegistryKey> getSubKeys() throws RegistryException {
		queryValues();
		return null;
	}

	/**
	 * Tests to see if this key exists in the registry.
	 * 
	 * @return <code>true</code> if the key exists in the registry;
	 *         <code>false</code> otherwise
	 * @throws RegistryException
	 */
	public boolean exists() throws RegistryException {
		String keyName = getName();
		QueryOperation op = new QueryOperation(keyName);
		long timeout = getTimeout();
		try {
			op.execute(timeout);
			return true;
		} catch (ExecuteException e) {
			return false;
		}
	}

	/**
	 * Queries the values of this key.
	 * 
	 * @throws RegistryException
	 */
	public Collection<RegistryValue> queryValues() throws RegistryException {
		String keyName = getName();
		QueryOperation op = new QueryOperation(keyName);
		doExecute(op);
		QueryOperation.Result result = op.getResult();
		return result.get(this);
	}

	public QueryOperation.Result querySubkeys(boolean recursive)
			throws RegistryException {
		String keyName = getName();
		QueryOperation op = new QueryOperation(keyName);
		op.setRecursive(recursive);
		doExecute(op);
		QueryOperation.Result result = op.getResult();
		if (result.containsKey(this))
			result.remove(this);
		return result;
	}

	/**
	 * Adds or overwrites this registry key.
	 * 
	 * @throws RegistryException
	 */
	public void add() throws RegistryException {
		String keyName = getName();
		AddOperation op = new AddOperation(keyName);
		doExecute(op);
	}

	/**
	 * Deletes this registry key.
	 * 
	 * @throws RegistryException
	 */
	public void delete() throws RegistryException {
		String keyName = getName();
		DeleteOperation op = new DeleteOperation(keyName);
		doExecute(op);
	}

	/**
	 * Deletes all values under this key.
	 * 
	 * @throws RegistryException
	 */
	public void deleteAll() throws RegistryException {
		String keyName = getName();
		DeleteOperation op = new DeleteOperation(keyName);
		op.setValue(DeleteOperation.Value.VA);
		doExecute(op);
	}

	public void copyTo(RegistryKey destination) throws RegistryException {
		boolean recursive = false;
		copyTo(destination, recursive);
	}

	public void copyTo(RegistryKey destination, boolean copySubkeys)
			throws RegistryException {
		String keyName = getName();
		String destinationKeyName = destination.getName();
		CopyOperation op = new CopyOperation(keyName, destinationKeyName);
		op.setCopySubkeys(copySubkeys);
		doExecute(op);
	}

	/**
	 * 
	 * @param fileName
	 *            The name of the disk file to save. If no path is specified,
	 *            the file is created in the current folder of the calling
	 *            process.
	 */
	public void save(String fileName) throws RegistryException {
		String keyName = getName();
		SaveOperation op = new SaveOperation(keyName, fileName);
		doExecute(op);
	}

	/**
	 * 
	 * @param fileName
	 *            The name of the hive file to restore.
	 */
	public void restore(String fileName) throws RegistryException {
		String keyName = getName();
		RestoreOperation op = new RestoreOperation(keyName, fileName);
		doExecute(op);
	}

	/**
	 * @param fileName
	 *            The name of the hive file to load.
	 */
	public void load(String fileName) throws RegistryException {
		String keyName = getName();
		LoadOperation op = new LoadOperation(keyName, fileName);
		doExecute(op);
	}

	public void unload() throws RegistryException {
		String keyName = getName();
		UnloadOperation op = new UnloadOperation(keyName);
		doExecute(op);
	}

	public void exportTo(String fileName) throws RegistryException {
		String keyName = getName();
		ExportOperation op = new ExportOperation(keyName, fileName);
		doExecute(op);
	}

	public void importFrom(String fileName) throws RegistryException {
		String keyName = getName();
		ImportOperation op = new ImportOperation(keyName, fileName);
		doExecute(op);
	}

	private void doExecute(Operation op) throws RegistryException {
		long timeout = getTimeout();
		try {
			op.execute(timeout);
		} catch (ExecuteException e) {
			throw new RegistryException(e);
		}
	}

	public String toString() {
		return getName();
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		else if (obj == null)
			return false;
		else if (getClass() != obj.getClass())
			return false;
		RegistryKey other = (RegistryKey) obj;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

}